import * as THREE from '../../../../modules/three/three.module.js'
import {
  createContourLineSegments, createGuideLine,
  flatten,
  getClosestSpan, getInvolvedMeshes,
  retrieveAdhereLines, unPreciseIndexOf
} from "../util/StaticMethods.js";
import {WorldConfig} from "../CommonConsts.js";
import World3dEvent from "../../scene/event/World3dEvent.js";
import SprayPath from "../../model/SprayPath.js";
import UndoType from "../../scene/undo/UndoType.js";

const mouseRay = new THREE.Ray();
const intersect = new THREE.Vector3();

const geometry = new THREE.OctahedronGeometry(0.05);
const mtl = new THREE.MeshBasicMaterial({color: '#06b', wireframe: true});
const pointMesh = new THREE.Mesh(geometry, mtl);

let vLine0, vLine1, hLine0, hLine1;
// 初始化吸附虚线
function initGuideLines(lineHolder) {
  let halfLen = 10000;
  const z = -0.05;
  let v0 = new THREE.Vector3(0, -halfLen, z), v1 = new THREE.Vector3(0, halfLen, z);
  let v2 = new THREE.Vector3(-halfLen, 0, z), v3 = new THREE.Vector3(halfLen, 0, z);
  vLine0 = createGuideLine([v0, v1], '#f00');
  vLine1 = createGuideLine([v0, v1], '#f00');
  hLine0 = createGuideLine([v2, v3], '#f00');
  hLine1 = createGuideLine([v2, v3], '#f00');
  const guideLines = [vLine0, vLine1, hLine0, hLine1];
  guideLines.forEach(line => {
    lineHolder.add(line);
    line.visible = false;
    line.renderOrder = 5;
  });
}

function hideGuideLines() {
  const guideLines = [vLine0, vLine1, hLine0, hLine1];
  guideLines.forEach(line => {
    line.visible = false;
  });
}

function updateGuideLines(xAdhere, yAdhere) {
  if (xAdhere) {
    const x0 = xAdhere.adhereTo[0] || Infinity;
    const x1 = xAdhere.adhereTo[1] || Infinity;
    showLine(vLine0, x0, true);
    showLine(vLine1, x1, true);
  }
  if (yAdhere) {
    const y0 = yAdhere.adhereTo[0] || Infinity;
    const y1 = yAdhere.adhereTo[1] || Infinity;
    showLine(hLine0, y0, false);
    showLine(hLine1, y1, false);
  }
}

function showLine(lineMesh, num, vertical) {
  if (num == Infinity) {
    lineMesh.visible = false
    return
  }
  lineMesh.visible = true
  if (vertical) {
    lineMesh.position.x = num
  } else {
    lineMesh.position.y = num
  }
}

/**
 * 创建用于表示已经喷涂过区域的网格
 * @param {THREE.Vector3 | null} from
 * @param {THREE.Vector3} to
 * @param {number} dx
 * @param {number} dy
 * @param {THREE.Color} color
 * @return {THREE.Mesh}
 */
function createSprayedMesh(from, to, dx, dy, color) {
  const hx = dx * 0.5;
  const hy = dy * 0.5;
  //   v3 -------- v2
  //   |           |
  //   v0 -------- v1
  const v0 = new THREE.Vector3(-hx, -hy, 0);
  const v1 = new THREE.Vector3(hx, -hy, 0);
  const v2 = new THREE.Vector3(hx, hy, 0);
  const v3 = new THREE.Vector3(-hx, hy, 0);
  const posAttr = new THREE.BufferAttribute(flatten([v0, v1, v2, v3]), 3);
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position', posAttr);
  geometry.setIndex([0, 3, 1, 1, 3, 2]);
  const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({color: color, transparent: true, opacity: 0.8}));
  mesh.position.copy(to);
  if (!from || from.manhattanDistanceTo(to) < 0.001) {
    return mesh;
  }
  if (Math.abs(from.y - to.y) < 0.001) {
    const pos = from.clone().add(to).multiplyScalar(0.5);
    if (to.x > from.x) {
      pos.x += hx;
    } else {
      pos.x -= hx;
    }
    const scale = Math.abs(from.x - to.x) / dx;
    mesh.position.copy(pos);
    mesh.scale.set(scale, 1, 1);
    return mesh;
  } else if (Math.abs(from.x - to.x) < 0.001) {
    const pos = from.clone().add(to).multiplyScalar(0.5);
    if (to.y > from.y) {
      pos.y += hy;
    } else {
      pos.y -= hy;
    }
    const scale = Math.abs(from.y - to.y) / dy;
    mesh.position.copy(pos);
    mesh.scale.set(1, scale, 1);
    return mesh;
  } else {
    const dir = to.clone().sub(from);
    const arr = [v0, v1, v2, v3].map(it => it.add(from));
    const dots = arr.map(it => it.dot(dir));
    const max = Math.max(...dots);
    const index = dots.indexOf(max);
    const preIndex = index === 0 ? 3 : index - 1;
    const nxtIndex = index === 3 ? 0 : index + 1;
    const a = arr[preIndex];
    const b = arr[index];
    const c = arr[nxtIndex];
    const d = c.clone().add(dir);
    const e = b.clone().add(dir);
    const f = a.clone().add(dir);
    const posAttr = new THREE.BufferAttribute(flatten([a, b, c, d, e, f]), 3);
    const geo = new THREE.BufferGeometry();
    geo.setAttribute('position', posAttr);
    geo.setIndex([1,2,3, 1,3,4, 1,4,5, 1,5,0]);
    const meshArrow = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({color: color, transparent: true, opacity: 0.8}));
    return meshArrow;
  }
}

// 负责编辑喷涂动画的无人机航线
export default class SprayPathMaker extends THREE.Group {
  /**
   * @type {THREE.Color}
   */
  color
  /**
   * @type {WorkArea}
   */
  workArea
  matInv = new THREE.Matrix4()
  localCenter = new THREE.Vector3()
  size = new THREE.Vector3()
  /**
   * 竖直吸附线
   * @type {number[]}
   */
  xLines = []
  /**
   * 水平吸附线
   * @type {number[]}
   */
  yLines = []
  /**
   * 工作区id列表
   * @type {number[]}
   */
  workAreaIds = []

  /**
   * 背景格子
   * @type {THREE.LineSegments}
   */
  grid
  /**
   * 光标网格
   * @type {THREE.Line}
   */
  movingSquare
  /**
   * 喷涂航线的航点
   * @type {THREE.Vector3[]}
   */
  pathPoints = []
  // 一次喷涂覆盖的宽度
  dx = 0.1
  // 一次喷涂覆盖的高度
  dy = 0.1

  /**
   * @param {EditRecord} recorder
   */
  constructor(recorder) {
    super();
    this.recorder = recorder;
  }

  /**
   * @param {World3d} world3d
   * @param {WorkArea} workArea
   * @param {boolean} single 为true时，只编辑一个工作区的航线
   */
  init(world3d, workArea, single) {
    this.applyMatrix4(workArea.planeMat);
    this.matInv.copy(workArea.planeMatInv);
    this.workArea = workArea;
    this.color = new THREE.Color();
    this.color.setStyle(World3dEvent.workAreaSelected.value.color);

    let areaMeshes;
    if (single) {
      areaMeshes = world3d.store.areaMeshes.filter(it => it.vo.id === workArea.id);
    } else {
      areaMeshes = getInvolvedMeshes(workArea.createPlane(), world3d.store.areaMeshes, []);
    }
    const workAreas = areaMeshes.map(it => it.vo);
    const yAdhereLines = [];
    const xAdhereLines = [];
    const box3 = new THREE.Box3();
    workAreas.forEach(workArea => {
      this.workAreaIds.push(workArea.id);
      retrieveAdhereLines(workArea, yAdhereLines, xAdhereLines);
      if (workArea.localBox3) {
        workArea.updateLocalBox();
      }
      box3.union(workArea.localBox3);
    });
    this.localBox3 = box3;
    const size = box3.getSize(new THREE.Vector3());
    box3.getCenter(this.localCenter);
    this._initSubsets(size);
    initGuideLines(this);
    this.xLines = xAdhereLines;
    this.yLines = yAdhereLines;
    this.areaMeshes = areaMeshes;
    return this;
  }

  getId() {
    const areaIds = this.workAreaIds.slice();
    areaIds.sort();
    const arr = [this.dx, this.dy].concat(areaIds);
    return arr.join(', ');
  }

  /**
   * 创建编辑过程中，用于交互的子组件
   * @param {{x: number, y: number}} size
   * @private
   */
  _initSubsets(size) {
    let dx = WorldConfig.spray_size_in_x;
    let dy = WorldConfig.spray_size_in_z;
    if (WorldConfig.spray_fixed_columns > 0) {
      dx = size.x / WorldConfig.spray_fixed_columns;
    }
    if (WorldConfig.spray_fixed_rows > 0) {
      dy = size.y / WorldConfig.spray_fixed_rows;
    }
    this.dx = dx;
    this.dy = dy;
    const dz = 0.05; // WorldConfig.spray_to_plane_distance
    const hw = size.x * 0.5;
    const hh = size.y * 0.5;
    const hx = dx * 0.5;
    const hy = dy * 0.5;
    // 光标网格创建
    const vx = hx * 1.0;
    const vy = hy * 1.0;
    const v0 = new THREE.Vector3(vx, vy, dz);
    const v1 = new THREE.Vector3(-vx, vy, dz);
    const v2 = new THREE.Vector3(-vx, -vy, dz);
    const v3 = new THREE.Vector3(vx, -vy, dz);
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(flatten([v0, v1, v2, v3, v0]), 3));
    // geometry.setIndex([0,1,3, 1,2,3]);
    this.movingSquare = new THREE.Line(geometry, new THREE.LineBasicMaterial({
      color: this.color,
      transparent: true,
      depthTest: false,
    }));
    this.add(this.movingSquare);
  }

  _appendToAdhereLines(point) {
    const x1 = point.x - this.dx * 0.5;
    const x2 = point.x + this.dx * 0.5;
    const y1 = point.y - this.dy * 0.5;
    const y2 = point.y + this.dy * 0.5;
    const addedX = [];
    const addedY = [];
    if (unPreciseIndexOf(this.xLines, x1) < 0) {
      this.xLines.push(x1);
      addedX.push(x1);
    }
    if (unPreciseIndexOf(this.xLines, x2) < 0) {
      this.xLines.push(x2);
      addedX.push(x2);
    }
    if (unPreciseIndexOf(this.yLines, y1) < 0) {
      this.yLines.push(y1);
      addedY.push(y1);
    }
    if (unPreciseIndexOf(this.yLines, y2) < 0) {
      this.yLines.push(y2);
      addedY.push(y2);
    }
    return {addedX, addedY};
  }

  createSprayPath() {
    if (!this.pathPoints.length) {
      return null;
    }
    const sprayPath = new SprayPath();
    sprayPath.color = this.color;
    sprayPath.cssColorStr = '#' + this.color.getHexString();
    sprayPath.workAreaIds = this.workAreaIds;
    sprayPath.facing = this.workArea.facing.clone();
    const distanceToWall = Math.max(sprayPath.distanceToWall, WorldConfig.spray_to_plane_distance);
    const offset = sprayPath.facing.clone().multiplyScalar(distanceToWall);
    this.pathPoints.forEach(point => {
      const wallPos = point.clone().applyMatrix4(this.workArea.planeMat);
      sprayPath.wallPath.push(wallPos);
      const dronePos = wallPos.clone().add(offset);
      sprayPath.dronePath.push(dronePos);
      sprayPath._sprayFlags.push(true);
    });
    sprayPath.editableRect.w = this.dx;
    sprayPath.editableRect.h = this.dy;
    return sprayPath;
  }

  // 保存操作栈中的数据
  saveToRecorderObj() {
    const stackUndo = [];
    this.recorder.stackUndo.forEach(editAction => {
      const {addedX, addedY, addedPoint} = editAction.context;
      const obj = {addedX, addedY, addedPoint: addedPoint.toArray()};
      stackUndo.push(obj);
    });
    return {stackUndo, fx: WorldConfig.spray_fixed_columns, fy: WorldConfig.spray_fixed_rows, dx: this.dx, dy: this.dy};
  }

  // 调用时机：this.init() 调用后
  loadFromRecorderObj(obj) {
    const editActions = obj.stackUndo;
    editActions.forEach((savedAction, index) => {
      const {addedX, addedY} = savedAction;
      const addedPoint = new THREE.Vector3().fromArray(savedAction.addedPoint);
      let prePoint = null;
      if (index > 0) {
        prePoint = new THREE.Vector3().fromArray(editActions[index - 1].addedPoint);
      }
      const sprayedMesh = createSprayedMesh(prePoint, addedPoint, this.dx, this.dy, this.color);
      sprayedMesh.name = 'sprayedMesh';
      this.add(sprayedMesh);
      const pMesh = pointMesh.clone();
      pMesh.position.copy(addedPoint);
      this.add(pMesh);
      pMesh.name = 'pathPointMesh';

      const ctx = {addedX, addedY, addedPoint, sprayedMesh, holder: this, xLines: this.xLines, yLines: this.yLines};
      ctx.pathPoints = this.pathPoints;
      ctx.addedPointMesh = pMesh;
      ctx.holder = this;
      if (index > 0) {
        const segment = createContourLineSegments([prePoint, addedPoint]);
        segment.name = 'segment';
        this.add(segment);
        ctx.addedSegment = segment;
      }
      this.recorder.addAction(UndoType.addSprayPathPoint, ctx);
      this.pathPoints.push(addedPoint);
      this.xLines.push(...addedX);
      this.yLines.push(...addedY);
    });
  }

  /**
   * 鼠标移动时 调用
   * @param {THREE.Raycaster} raycaster
   */
  onMouseMove(raycaster) {
    hideGuideLines();
    mouseRay.copy(raycaster.ray).applyMatrix4(this.matInv);
    const inter = mouseRay.intersectBox(this.localBox3, intersect);
    if (!inter) {
      return;
    }
    this.movingSquare.position.copy(inter);
    // if (KeyboardState.keyDownAlt) { // 不进行吸附处理
    //   return;
    // } else {
      const xArr = [inter.x - this.dx * 0.5, inter.x + this.dx * 0.5];
      const yArr = [inter.y - this.dy * 0.5, inter.y + this.dy * 0.5];
      const xAdhere = getClosestSpan(xArr, this.xLines);
      const yAdhere = getClosestSpan(yArr, this.yLines);
      if (xAdhere) {
        this.movingSquare.position.x += xAdhere.span;
      }
      if (yAdhere) {
        this.movingSquare.position.y += yAdhere.span;
      }
      updateGuideLines(xAdhere, yAdhere);
    // }
  }

  // 鼠标弹起时，添加喷涂航线的航点
  onMouseUp() {
    hideGuideLines();
    const point = this.movingSquare.position.clone();
    const {addedX, addedY} = this._appendToAdhereLines(point);
    const ctx = {addedX, addedY, xLines: this.xLines, yLines: this.yLines, holder: this};
    this.pathPoints.push(point);
    const pMesh = pointMesh.clone();
    pMesh.position.copy(point);
    this.add(pMesh);
    pMesh.name = 'pathPointMesh';
    ctx.pathPoints = this.pathPoints;
    ctx.addedPoint = point;
    ctx.addedPointMesh = pMesh;

    let sprayedMesh;
    if (this.pathPoints.length > 1) {
      // 多于一个航点时，新增一段航线 线段
      const p0 = this.pathPoints[this.pathPoints.length - 2];
      const p1 = this.pathPoints[this.pathPoints.length - 1];
      const segment = createContourLineSegments([p0, p1]);
      segment.name = 'segment';
      // segment.position.copy(this.localCenter);
      this.add(segment);
      sprayedMesh = createSprayedMesh(p0, p1, this.dx, this.dy, this.color);
      ctx.addedSegment = segment;
    } else {
      sprayedMesh = createSprayedMesh(null, point, this.dx, this.dy, this.color);
    }
    sprayedMesh.name = 'sprayedMesh';
    this.add(sprayedMesh);
    ctx.sprayedMesh = sprayedMesh;

    this.recorder.addAction(UndoType.addSprayPathPoint, ctx);
  }
}
